#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::cin;
using std::string;

/*
    ssize_t和socklen_t是C语言中的两种数据类型，通常用于网络编程和系统编程中。

    ssize_t是一种用于表示字节数或错误码的有符号整数类型。
    它通常被用来表示读取或写入操作的字节数，以及系统调用返回的错误码。
    在网络编程中，ssize_t常常用于表示从套接字读取或写入的字节数，或者表示I/O操作的返回值。

    socklen_t是一种用于表示套接字地址长度的数据类型。
    在网络编程中，套接字地址长度是一个常见的参数，用于指定套接字地址结构体的长度。
    socklen_t通常用于表示套接字地址结构体的长度，以确保在不同平台和系统上都能正确地处理套接字地址长度的问题。

    总之，ssize_t用于表示字节数和错误码，而socklen_t用于表示套接字地址的长度。
    它们在网络编程和系统编程中经常被使用，以确保对数据大小和套接字地址的正确处理。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    这是一个典型的C语言程序的main函数定义以及传参。

    在C语言中，main函数通常具有以下形式：

    int main(int argc, char *argv[])
    其中，argc代表参数的个数，
    argv是一个指向字符串数组的指针，每个字符串代表一个传入的参数。
    一般来说，argv[0]存储程序的名称，argv[1]、argv[2]等依次存储传入的参数值。

    因此，./a.out 127.0.0.1 8888表示程序被传入了三个参数：
    程序名称为./a.out，第一个参数为127.0.0.1，第二个参数为8888。

*/

int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        cout << "argc < 3" << endl;
        return -1;
    }
    //1、调用socket函数，创建文件描述符
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
        perror("socket");
        return -1;
    }

    //2、执行bind函数，绑定服务器的ip与端口号
    struct sockaddr_in addr;//创建sockaddr_in的变量
    memset(&addr, 0, sizeof(addr));//初始化
    
    //绑定服务器的ip与端口号
    addr.sin_family = AF_INET;
    //cout<<argv[0]<<endl;
    addr.sin_port = htons(atoi(argv[2]));
    addr.sin_addr.s_addr = inet_addr(argv[1]);

    int ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    //3、执行listen函数，让服务器处于监听状态
    ret = listen(listenfd, 128);
    if(ret < 0)
    {
        perror("listen");
        return -1;
    }
/*
    在服务器端，通常只有一个监听套接字（listening socket），这个套接字负责接受传入的连接请求。
    每当有新的客户端请求连接时，服务器会调用accept函数来创建一个新的套接字，
    这个新的套接字会用于与该客户端进行通信。
    因此，对于每个客户端连接，服务器会有一个新的套接字来处理通信，
    
    而监听套接字仍然保持用于接受新的连接请求。

    这种方式允许服务器同时处理多个客户端的连接请求，并且为每个客户端分配独立的套接字来进行通信。
    

    当服务器处于监听状态时，如果暂时没有新连接请求进来，服务器会停留在accept函数这里等待新的连接请求。
    此时服务器是阻塞状态。
    因为accept函数是一个阻塞函数，当没有新的连接请求到来时，accept函数会一直等待，直到有新的连接请求到来才会返回，
    这种方式可以让服务器保持监听状态，随时接受新的客户端连接请求，实现了多客户端并发的功能。
*/
    cout << "server is listening..." << endl;
    
    //4、执行accept函数，让服务器阻塞等待客户端的连接
#if 0
    //使用接下来几行代码接收客户端的ip与端口
    struct sockaddr_in cliAddr;
    socklen_t addrlen = sizeof(cliAddr);
    int connfd = accept(listenfd, (struct sockaddr *)&cliAddr, &addrlen);
#endif
    int connfd = accept(listenfd, nullptr, nullptr);
    if(connfd < 0)
    {
        perror("accept");
        return -1;
    }

    //5、读写数据（让客户端与服务器传输数据，这里采用的是客户端说一句，服务器说一句）
    while(1)
    {
        char buf[128] ={0};
        //服务器接收客户端的数据
        ssize_t len  = recv(connfd, buf, sizeof(buf), 0);
        if(len > 0)
        {
            cout << ">>recv client msg : " << buf << endl;
        }
        else if(0 == len)
        {
            cout << "len == 0" << endl;
        }
        else
        {
            cout << "服务器读取数据失败 " << endl;
        }

        //让服务器发送数据给客户端
        string line;
        /* cin >> line; */
        getline(cin, line);
        ssize_t len2 = send(connfd, line.c_str(), line.size(), 0);
        if(len2 > 0)
        {
            cout << "服务器发送数据正常" << endl;
        }
        else if(0 == len2)
        {
            cout << "服务器发送数据len2 == 0 " << endl;
        }
        else
        {
            cout << "服务器发送数据失败" << endl;
        }
    }

    //6、执行close函数，关闭文件描述符
    close(listenfd);
    close(connfd);
    return 0;
}

